// Copyright © 1993,4 Apple Computer, Inc. All rights reserved

// application constants
constant kAppSymbol := '|SoupTDS:PIEDTS| ;
constant kPackageName := "soupTDS:PIEDTS" ;
constant kAppName := "MetaData";

// soup constants
constant kAppSoupName := kPackageName ;
constant kSoupIndices := '[] ;
constant kAppObject := '["Thing that doesn't suck", "Things that don't suck"];

// metadata constants
constant kMetaDataVersion := 2 ;	// local revision number of our metadata spec

// a date format to use
DefConst('kAppDateFormat,
	GetDateStringSpec([[kElementHour, kFOrmatNumeric],
							 [kElementMinute, kFormatNumeric],
							 [kElementAMPM, kFormatAbbr]]));

// ---- End Project Data ----


// ---- File Sync Another One.t ----

// Before Script for "MetaDataApp"
// Copyright © 1993,4 Apple Computer, Inc. All rights reserved

MetaDataApp :=
   {
    viewSetupDoneScript:
      func()
      begin
         tdsSoup := call kRegisterCardSoupFunc with
      			(kAppSoupName, kSoupIndices, kAppSymbol, kAppObject);
         // tdsCursor := Query(tdsSoup, {type: 'index, indexPath: 'aString});
         tdsCursor := Query(tdsSoup, {type: 'index});
         if NOT tdsCursor:Entry() then
            :FillSoup();
      	:InitMetaData();
         theSoupFrame := tdsCursor:Reset();
      end,
    viewQuitScript:
      func()
      begin
      	// unregister card soups
         call kUnregisterCardSoupFunc with (kAppSoupName) ;
      
      	// cleanup soup and cursor references
      	tdsCursor := nil ;
      	tdsSoup := nil ;
      
      	// cleanup other extraneous references
      	theSoupFrame := nil ;
      	theTextFrame := nil ;
      end,
    theTextFrame: nil,
    tdsSoup:
      nil
      
      // holds the soup at runtime,
    TestImport:
      func()
      begin
      	if theTextFrame then
      	begin
      	   theSoupFrame := MetaData:import(theTextFrame);
      	   print(theSoupFrame);
      	end else
      		:Notify(kNotifyAlert, EnsureInternal(kAppName),
      					EnsureInternal("You must first test exporting by pressing the 'Test Export Script' button"));
      end,
    viewBounds: {left: 0, top: 0, right: 240, bottom: 150},
    appObject: kAppObject,
    _proto: protoApp,
    FillSoup:
      func()
      begin
         local i;
         print("Filling Soup");
        	for i := 1 to 20 do
           	tdsSoup:AddToDefaultStore({
              	aString: GetRandomWord(5,20) & $- & GetRandomWord(5,20),
              	anInteger: Random(0,10000),
              	aReal: Random(0, 10000)/100,
              	aSymbol: MySymbolArray[Random(0, length(MySymbolArray)-1)],
             	aDate: Time(),
               	aTime: Time(),
              	aDateAndTime: Time(),
              	aCharacter: CHR(Random(ORD($A),ORD($Z))),
              	aBoolean: if Random(0, 1) = 1 then TRUE else NIL,
              	aFolder: if Random(0, 2) = 0 then nil /* Unfiled */ else
              	         (foreach folder, name in userConfiguration.userFolders
              	          collect folder)[
              	            Random(0, length(userConfiguration.userFolders)-1)],
              	aFrame: {
              	   aStringSlot: GetRandomWord(1,5),
              	   anIntegerSlot: Random(0, 100),
              	},
              	anArray: [Random(0, 9), Random(10,19), Random(20,29)],
           	});
      	if NOT tdsCursor then
         	tdsCursor := Query(tdsSoup, {type: 'index});
         theSoupFrame := tdsCursor:Reset();
      end,
    DestroySoup:
      func()
      begin
         local store, myCursor, mySoup, directorySoup;
      
      	// unregister soups with system
      	call kUnRegisterCardSoupFunc with (kAppSoupName);
      
      
         // remove the soup from each store currently installed
         foreach store in GetStores() do
            if mySoup := store:GetSoup(kAppSoupName) then
               mySoup:RemoveFromStore();
      
         // remove the soup MetaData
         directorySoup := GetStores()[0]:GetSoup("Directory");
         if directorySoup then
         begin
            myCursor := Query(directorySoup, {type:'index, indexPath:'soup,
                              startKey: kAppSoupName,
                              endTest: func(entry) NOT(StrEqual(entry.soup, kAppSoupName))});
            while myCursor:Entry() do
            begin
               EntryRemoveFromSoup(myCursor:Entry());
               myCursor:Next();
            end;
         end;
      	
      	// nil out the references to the soup and cursors
      	tdsSoup := nil ;
      	tdsCursor := nil ;
      	theSoupFrame := nil;
      end,
    theSoupFrame: nil,
    appSymbol: kAppSymbol,
    title: "Meta Data Sample",
    MetaData:
      {
      // name of soup within Newton
      soup: kAppSoupName,
      
      // alternate name to show the user
      name: "Meta Data Sample Soup",
      
      // version number, default 1
      version: kMetaDataVersion,
      
      // default frame for a new entry
      newEntry: {
         aString: "default",
         anInteger: 0,
         aReal: 0.0,
         aSymbol: nil,
         aDate: nil,         // check out the export script (heh)
         aTime: nil,         // check out the export script (heh)
         aDateAndTime: nil,  // check out the export script (heh)
         aCharacter: $?,
         aBoolean: NIL,
         aFolder: NIL,
         aFrame: {
            aStringSlot: "default",
            anIntegerSlot: 0,
         },
         anArray: Array(3, 0),
      },
      
      // displayInfo/editInfo/exportInfo entries can have the folowing slots:
      //   slot:      symbol that refers to slot in soup entry being described
      //   type:      type of editor for slot, default 'string
      //      'string, 'char, 'text, 'integer, 'picklist, 'folder, 'e_line
      //      'date, 'time, 'real, 'phone (last 4 not fully implemented)
      //   choices:   in editInfo, array of strings for popups ('picklist)
      //   size:      number of '0' characters that should fit, default 15
      //   hidden:    if true, column will initially be hidden
      //   label:     string for label on Mac/PC, default is slot name
      //   sameLine:  for editInfo only, if true will put on same line as previous
      //   unsearchable: for displayInfo, if true will not allow searches on this field
      //   path:      specifies path to data for search comparisons, defaults to slot
      //   compareScript: symbol for which compare script to use.
      // These are reserved for future use, not implemented now
      //   justify:   'right, 'left, 'center, or 'system
      //   maxChars:   upper limit for input in editInfo
      //   minChars:   lower limit for input in editInfo
      //   maxValue:   max for numeric fields in editInfo
      //   minValue:   min for numeric fields in editInfo
      //   validChar:  script (in meta data) that will be called for
      //               each character in editInfo to validate it.
      //   validField: script (in meta data) that will be called when
      //               the user finishes the field.
      //   numLines:   specifies the number of lines for editInfo
      
      // used in overview list on Mac/PC, order in array controls Mac/PC
      displayInfo: ['aString, 'anInteger, 'aReal, 'aSymbol, 'aDate, 'aTime,
                    'aDateAndTime, 'aCharacter, 'aBoolean, 'aFolder,],
      //              'aFrame1, 'aFrame2, 'anArray1, 'anArray2, 'anArray3],
      
      // used in item list on Mac/PC
      editInfo: ['aString, 'anInteger, 'aReal, 'aSymbol, 'e_line, 'aDate, 'aTime,
                 'aDateAndTime, 'e_line, 'aCharacter, 'aBoolean, 'aFolder, 'e_line,
                 'aFrame1, 'aFrame2, 'anArray1, 'anArray2, 'anArray3],
      
      // used for import/export on Mac/PC
      exportInfo: ['aString, 'anInteger, 'aReal, 'aSymbol, 'aDate, 'aTime,
                   'aDateAndTime, 'aCharacter, 'aBoolean, 'aFolder,
                   'aFrame1, 'aFrame2, 'anArray1, 'anArray2, 'anArray3],
      
      // specifies defaults in displayInfo, editInfo, exportInfo
      defaultDefinitions: {
         aString: {slot: 'aString, size: 41, },
         anInteger: {slot: 'anInteger, type: 'integer, },
         aReal: {slot: 'aReal, type: 'real, },
         aSymbol: {slot: 'aSymbol, type: 'mySymbolType, size: 12, },
         aDate: {slot: 'aDate, type: 'date, },
         aTime: {slot: 'aTime, type: 'time, sameline: TRUE, },
         aDateAndTime: {slot: 'aDateAndTime, type: 'myDateAndTime, },
         aCharacter: {slot: 'aCharacter, type: 'char, },
         aBoolean: {slot: 'aBoolean, type: 'picklist, label: "Boolean", choices: ["TRUE", "FALSE"], },
         aFolder: {slot: 'aFolder, type: 'folder, },
         aFrame1: {slot: 'aFrame.aStringSlot, hidden: TRUE, size: 11, },
         aFrame2: {slot: 'aFrame.anIntegerSlot, type: 'integer, hidden: TRUE, },
         anArray1: {slot: [pathExpr: 'anArray, 0], type: 'integer, hidden: TRUE, },
         anArray2: {slot: [pathExpr: 'anArray, 1], type: 'integer, hidden: TRUE, },
         anArray3: {slot: [pathExpr: 'anArray, 2], type: 'integer, hidden: TRUE, },
      },
      
      // used to lookup values of type slots
      // must be one of:
      //      'string, 'char, 'text, 'integer, 'picklist, 'folder, 'e_line
      //      'date, 'time, 'real, 'phone (last 4 not fully implemented)
      editTypes: {
         mySymbolType: {type: 'picklist, label: "Symbol", choices: ["first", "second", "third", ], },
      },
      
      // text frames have same slots as soup frames, but value
      // for each slot is a string.  The textFrame should also have a slot called
      // originalFrame, which should point to the soup frame (just a convention)
      // Note that the text frame passed to the import script will NOT be the same
      // frame returned from the export script.  (Any slots not mentioned in editInfo
      // won't appear in the frame passed to the import script.)
      
      export: func(theSoupFrame)
      begin
         local theTextFrame := {
            // originalFrame: theSoupFrame,
            aString: theSoupFrame.aString,
            anInteger: SPrintObject(theSoupFrame.anInteger),
            aReal: SPrintObject(theSoupFrame.aReal),
            aSymbol: :MySymbolToString(theSoupFrame.aSymbol),
            aDate: ShortDateStr(if NOT theSoupFrame.aDate then Time() else theSoupFrame.aDate, kIncludeAllElements),
            aTime: TimeStr(if NOT theSoupFrame.aTime then Time() else theSoupFrame.aTime,kAppDateFormat),
            aDateAndTime: ShortDateStr(if NOT theSoupFrame.aDate then Time() else theSoupFrame.aDate, kIncludeAllElements)
                          && TimeStr(if NOT theSoupFrame.aTime then Time() else theSoupFrame.aTime, kAppDateFormat),
            aCharacter: SPrintObject(theSoupFrame.aCharacter),
            aBoolean: defaultDefinitions.aBoolean.choices[if theSoupFrame.aBoolean then 0 else 1],
            aFolder: if theSoupFrame.aFolder then SPrintObject(theSoupFrame.aFolder),
            aFrame1: theSoupFrame.aFrame.aStringSlot,
            aFrame2: SPrintObject(theSoupFrame.aFrame.anIntegerSlot),
            anArray1: SPrintObject(theSoupFrame.anArray[0]),
            anArray2: SPrintObject(theSoupFrame.anArray[1]),
            anArray3: SPrintObject(theSoupFrame.anArray[2]),
         };
         return theTextFrame
      end,
      
      // convert a text frame to a soup entry frame
      import: func(theTextFrame)
      begin
         local n := StringToNumber(theTextFrame.anInteger);
         local m := StringToNumber(theTextFrame.aFrame2);
         local o := [
            StringToNumber(theTextFrame.anArray1),
            StringToNumber(theTextFrame.anArray2),
            StringToNumber(theTextFrame.anArray3),
         ];
         local theSoupFrame := {
            aString: theTextFrame.aString,
            anInteger: if n = abs(n) then floor(n) else ceiling(n),
            aReal: StringToNumber(theTextFrame.aReal),
            aSymbol: :StringToMySymbol(theTextFrame.aSymbol),
            aDate: StringToDate(theTextFrame.aDate),
            aTime: :GetTime(theTextFrame.atime),
            aDateAndTime: StringToDate(theTextFrame.aDateAndTime),
            aCharacter: if length(theTextFrame.aCharacter) > 0 then theTextFrame.aCharacter[0] else $ ,
            aBoolean: StrCompare(theTextFrame.aBoolean, defaultDefinitions.aBoolean.choices[0]) = 0,
            aFolder: if theTextFrame.aFolder then :GetSymbol(theTextFrame.aFolder),
            aFrame: {
               aStringSlot: theTextFrame.aFrame1,
               anIntegerSlot: if m = abs(m) then floor(m) else ceiling(m),
            },
            anArray: [
               if o[0] = abs(o[0]) then floor(o[0]) else ceiling(o[0]),
               if o[1] = abs(o[1]) then floor(o[1]) else ceiling(o[1]),
               if o[2] = abs(o[2]) then floor(o[2]) else ceiling(o[2]),
            ],
         };
         return theSoupFrame;
      end,
      
      // these supporting functions are used by import/export to
      // convert strings to symbols and vice-versa.  They're accessible
      // because the context for the import and export scripts is a copy
      // of the meta data frame itself.
      MySymbolToString: func(theSymbol)
         editTypes.mySymbolType.choices[
            if NOT theSymbol or theSymbol = 'firstSymbol then 0
            else if theSymbol = 'secondSymbol then 1
            else if theSymbol = 'thirdSymbol then 2
         ],
      
      StringToMySymbol: func(theString)
         if StrCompare(theString, editTypes.mySymbolType.choices[0]) = 0 then 'firstSymbol
         else if StrCompare(theString, editTypes.mySymbolType.choices[1]) = 0 then 'secondSymbol
         else if StrCompare(theString, editTypes.mySymbolType.choices[2]) = 0 then 'thirdSymbol
         else 'firstSymbol, 
      
      // need to put a wrapper around the functions that could make a symbol
      // 1.0 uses MakeSymbol, 2.0 uses Intern
      GetSymbol: func(string)
      	if functions.Intern then
      		Intern(string)
      	else
      		MakeSymbol(String),
      
      // yet another function to account for differences between 1.0 and 2.0
      // in 1.0, StringToTime is broken, so you have to use StringToDate,
      // but this will add todays date to the time.
      GetTime: func(string)
      	if ClassOf(StringToTime("11:00 am")) = 'frame then
      		StringToDate(string)
      	else
      		StringToTime(string),
      
      
      // used to write custom comparisons for fields.
      // Slots are references by compareScript slots in displayInfo array.
      // description contains:
      //   slotname:   path to data within the current frame
      //   datatype:   value of 'type slot in frame
      //   searchtype: search requested by user (0-9)
      //   searchtext: what the user typed in
      //   searchvalue: use to store converted values from searchtext
      RealCompare: func(entry, description)
         begin
            TRUE;
         end,
      
      compareScripts: {
         aRealCompare: func (entry, description) :RealCompare(entry, description),
         aSymbolCompare: func (entry, description) TRUE,
      },
      },
    tdsCursor: nil,
    TestExport:
      func()
      begin
      	if theSoupFrame then
      	begin
         	theTextFrame := MetaData:export(theSoupFrame);
         	print(theTextFrame);
      	end else
      		:Notify(kNotifyAlert, EnsureInternal(kAppName),
      				EnsureInternal("There is no soup, create one by pressing the 'Rebuild the Soup!' button"));
      end,
    MySymbolArray:
      [
      'firstSymbol,
      'secondSymbol,
      'thirdSymbol,
      ],
    TestNewEntry:
      func()
      begin
      	if tdsSoup then
      	begin
      		theSoupFrame := DeepClone(MetaData.newEntry);
      		tdsSoup:Add(theSoupFrame);
      		theTextFrame := MetaData:export(theSoupFrame);
      		print(theTextFrame);
      	end else
      		:Notify(kNotifyAlert, EnsureInternal(kAppName),
      					EnsureInternal("There is no soup to add to. Rebuild using the 'Rebuild the Soup!' button"));
      end,
    debug: "MetaDataApp",
    InitMetaData:
      func()
      begin
         // remove any old soup MetaData then add the latest
      	local addEntry := nil ;
         local directorySoup := GetStores()[0]:GetSoup("Directory");
         if directorySoup then
         begin
      		// get a the metaData soup entry
            myCursor := Query(directorySoup, {type:'index, indexPath:'soup,
                              startKey: kAppSoupName,
                              endTest: func(entry) NOT(StrEqual(entry.soup, kAppSoupName))});
      
      		// if the metaData spec is not the latest version
      		// for the app, remove it
      		if myCursor:Entry() AND (myCursor:Entry().version < kMetaDataVersion) then
      		begin
              	EntryRemoveFromSoup(myCursor:Entry());
      			addEntry := true ;
      			// if this happens, could be other stuff to remove
      			while myCursor:Next() do
              		EntryRemoveFromSoup(myCursor:Entry());
      		end;
      
      		// if needed, add a metaData entry
      		if addEntry then
           		directorySoup:Add(Clone(MetaData));
         end;
      end
   };

testExportButton := /* child of MetaDataApp */
   {text: "Test Export Script",
    buttonClickScript:
      func()
      begin
         :Parent():TestExport();
      end,
    viewBounds: {left: 28, top: 20, right: 212, bottom: 36},
    _proto: protoTextButton,
    debug: "testExportButton"
   };



testImportButton := /* child of MetaDataApp */
   {text: "Test Import Script",
    buttonClickScript:
      func()
      begin
         :Parent():TestImport();
      end,
    viewBounds: {left: 28, top: 44, right: 212, bottom: 60},
    _proto: protoTextButton,
    debug: "testImportButton"
   };



testNewButton := /* child of MetaDataApp */
   {text: "Test New Entry",
    buttonClickScript:
      func()
      begin
         :Parent():TestNewEntry();
      end,
    viewBounds: {left: 28, top: 68, right: 212, bottom: 84},
    _proto: protoTextButton,
    debug: "testNewButton"
   };



rebuildSoup := /* child of MetaDataApp */
   {text: "Rebuild the Soup!",
    buttonClickScript:
      func()
      begin
         foreach store in GetStores() do begin
            if soup := store:GetSoup(kAppSoupName) then
               soup:RemoveAllEntries();
      	end;
      	if not tdsSoup then
      		tdsSoup := call kRegisterCardSoupFunc with
      			(kAppSoupName, kSoupIndices, kAppSymbol, kAppObject);
         :FillSoup();
      end,
    viewBounds: {left: 28, top: 92, right: 210, bottom: 106},
    _proto: protoTextButton,
    debug: "rebuildSoup"
   };



zapSoup := /* child of MetaDataApp */
   {text: "Delete the Soup!",
    buttonClickScript:
      func()
      begin
         :DestroySoup();
      end,
    viewBounds: {left: 28, top: 116, right: 210, bottom: 130},
    _proto: protoTextButton,
    debug: "zapSoup"
   };







// ---- Beginning of section for non used Layout files ----

// End of output